package org.apache.ibatis.scripting.defaults;

import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

/**
 * 在BoundSql中记录的SQL语句中可能包含"?"占位符，
 * 而每个"?"占位符都对应了BoundSql.parameterMappings集合中的一个元素，
 * 在该ParameterMapping对象中记录了对应的参数名称以及该参数的相关属性
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class DefaultParameterHandler implements ParameterHandler {

    /**
     * 管理MyBatis中的全部TypeHandler对象
     */
    private final TypeHandlerRegistry typeHandlerRegistry;

    /**
     * MappedStatement对象，其中记录SQL节点相应的配置信息
     */
    private final MappedStatement mappedStatement;
    /**
     * 用户传入的实参对象
     */
    private final Object parameterObject;
    /**
     * 对应的BoundSql对象，需要设置参数的PreparedStatement对象，
     * 就是根据该BoundSql中记录的SQL语句创建的，BoundSql中也记录了对应参数的名称和相关属性
     */
    private final BoundSql boundSql;
    private final Configuration configuration;

    public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        this.mappedStatement = mappedStatement;
        this.configuration = mappedStatement.getConfiguration();
        this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
        this.parameterObject = parameterObject;
        this.boundSql = boundSql;
    }

    @Override
    public Object getParameterObject() {
        return parameterObject;
    }

    @Override
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        // 取出sql中的参数映射列表
        /*
         * 从 BoundSql 中获取 ParameterMapping 列表，每个 ParameterMapping
         * 与原始 SQL 中的 #{xxx} 占位符一一对应
         */
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                // 过滤掉存储过程中的输出参数
                // 检测参数类型，排除掉 mode 为 OUT 类型的 parameterMapping
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    // 获取属性名
                    String propertyName = parameterMapping.getProperty();
                    // issue #448 ask first for additional params
                    // 检测 BoundSql 的 additionalParameters 是否包含 propertyName
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                        // 检测运行时参数是否有相应的类型解析器
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        /*
                         * 若运行时参数的类型有相应的类型处理器 TypeHandler，则将
                         * parameterObject 设为当前属性的值。
                         */
                        value = parameterObject;
                    } else {
                        // 为用户传入的参数 parameterObject 创建元信息对象
                        // 获取对象中相应的属性值或查找Map对象中值
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        // 从用户传入的参数中获取 propertyName 对应的值
                        value = metaObject.getValue(propertyName);
                    }

                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        // 此处 jdbcType = JdbcType.OTHER
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    try {
                        // 通过TypeHandler.setParameter()方法会调用PreparedStatement.set*()方法为SQL语句绑定相应的实参
                        // 为SQL语句绑定完实参之后，就可以调用Statement对象相应的execute()方法，将SQL语句交给数据库执行了
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (TypeException e) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                    } catch (SQLException e) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                    }
                }
            }
        }
    }

}
